import { Map } from "common/types";
import * as geom from "common/geom";
import * as mvc from "common/mvc";

import * as dbtps from "../types";
import * as model from "../model/dragonBones";
/**
 * 坐标系垂直正方向是否向下。
 */
export let yDown: boolean = true;
/**
 * 骨架视图。
 */
export class ArmatureView extends mvc.View<model.Armature, any> {
    public static toString(): string {
        return "[class dragonBones.view.ArmatureView]";
    }

    public time: number = 0.0;
    public flipX: boolean = false;
    public flipY: boolean = false;
    public bones: BoneView[] = [];
    public slots: SlotView[] = [];
    public meshs: MeshView[] = [];
    public animations: AnimationView[] = [];

    private _bonesDirty: boolean = false;
    private _slotsDirty: boolean = false;
    private _animationsDirty: boolean = false;

    private _sortBones(): void {

    }

    private _sortSlots(): void {

    }

    private _sortAnimations(): void {

    }

    protected _initialize(): void {
        this._addNotification(dbtps.MNotificationType.ArmatureAddBone, this._modelNotificationHandler);
        this._addNotification(dbtps.MNotificationType.ArmatureRemoveBone, this._modelNotificationHandler);
        this._addNotification(dbtps.MNotificationType.BoneParentChange, this._modelNotificationHandler);

        this._addNotification(dbtps.MNotificationType.ArmatureAddSlot, this._modelNotificationHandler);
        this._addNotification(dbtps.MNotificationType.ArmatureRemoveSlot, this._modelNotificationHandler);
        this._addNotification(dbtps.MNotificationType.SlotParentChange, this._modelNotificationHandler);

        this._addNotification(dbtps.MNotificationType.ArmatureFrameRateChange, this._modelNotificationHandler);
    }

    protected _modelNotificationHandler(notification: mvc.Notification): void {
        switch (notification.type) {
            case dbtps.MNotificationType.ArmatureAddBone:
                {
                    const model = notification.target as model.Bone;
                    if (model.armature === this.model) {
                        this._bonesDirty = true;

                        const view = BoneView.create(BoneView);
                        this.bones.push(view);
                        view._armature = this;
                        view.initialize(model, null);
                    }
                }
                break;

            case dbtps.MNotificationType.ArmatureRemoveBone:
                {
                    const model = notification.target as model.Bone;
                    if (model.armature === this.model) {
                        const view = this.getBone(model);
                        console.assert(view !== null);
                        this.bones.splice(this.bones.indexOf(view!), 1);
                        view!.returnToPool();
                    }
                }
                break;

            case dbtps.MNotificationType.BoneParentChange:
                {
                    const model = notification.target as model.Bone;
                    if (model.armature === this.model) {
                        this._bonesDirty = true;

                        const view = this.getBone(model);
                        console.assert(view !== null);
                        view!._parentDirty = true;
                    }
                }
                break;

            case dbtps.MNotificationType.ArmatureAddSlot:
                {
                    const model = notification.target as model.Slot;
                    if (model.armature === this.model) {
                        this._slotsDirty = true;

                        const view = SlotView.create(SlotView);
                        this.slots.push(view);
                        view._armature = this;
                        view.initialize(model, null);
                    }
                }
                break;

            case dbtps.MNotificationType.ArmatureRemoveSlot:
                {
                    const model = notification.target as model.Slot;
                    if (model.armature === this.model) {
                        const view = this.getSlot(model);
                        console.assert(view !== null);
                        this.slots.splice(this.slots.indexOf(view!), 1);
                        view!.returnToPool();
                    }
                }
                break;

            case dbtps.MNotificationType.SlotParentChange:
                {
                    const model = notification.target as model.Slot;
                    if (model.armature === this.model) {
                        this._slotsDirty = true;

                        const view = this.getSlot(model);
                        console.assert(view !== null);
                        view!._parentDirty = true;
                    }
                }
                break;
        }
    }
    /**
     * 更新。
     */
    public update(): void {
        if (this._bonesDirty) {
            this._sortBones();

            this._bonesDirty = false;
        }

        if (this._slotsDirty) {
            this._sortSlots();

            this._slotsDirty = false;
        }

        if (this._animationsDirty) {
            this._sortAnimations();

            this._animationsDirty = false;
        }

        for (const bone of this.bones) {
            bone.reset();
        }

        for (const animation of this.animations) {
            animation.update();
        }

        for (const bone of this.bones) {
            bone.update();
        }

        for (const slot of this.slots) {
            slot.update();
        }

        for (const mesh of this.meshs) {
            mesh.update();
        }
    }

    public getBone(model: model.Bone): BoneView | null {
        return mvc.getViewByModel(model, this.bones);
    }

    public getSlot(model: model.Slot): SlotView | null {
        return mvc.getViewByModel(model, this.slots);
    }

    public getMesh(model: model.Mesh): MeshView | null {
        return mvc.getViewByModel(model, this.meshs);
    }

    public getAnimation(model: model.Animation): AnimationView | null {
        return mvc.getViewByModel(model, this.animations);
    }
}
/**
 * 骨骼视图。
 */
export class BoneView extends mvc.View<model.Bone, any> {
    public static toString(): string {
        return "[class dragonBones.view.BoneView]";
    }

    public local: geom.Transform = new geom.Transform();
    public global: geom.Transform = new geom.Transform();
    public globalMatrix: geom.Matrix = new geom.Matrix();
    /**
     * @internal
     * @private
     */
    public _parentDirty: boolean = false;
    /**
     * @internal
     * @private
     */
    public _transformDirty: boolean = false;
    /**
     * @internal
     * @private
     */
    public _childTransformDirty: boolean = false;
    /**
     * @internal
     * @private
     */
    public _armature: ArmatureView;
    private _parent: BoneView | null = null;

    protected _initialize(): void {
        console.assert(this._armature !== null);
        this._parentDirty = true;
    }
    /**
     * 本地重置为 pose。
     */
    public reset(): void {
        this.local.copyFrom(this.model.transform);
    }
    /**
     * 更新。
     */
    public update(): void {
        const model = this.model;

        if (this._parentDirty) {
            if (model.parent) {
                this._parent = this._armature.getBone(model.parent);
            }
            else {
                this._parent = null;
            }

            this._parentDirty = false;
            this._transformDirty = true;
        }

        if (
            this._transformDirty ||
            (this._parent !== null && this._parent._childTransformDirty)
        ) {
            const flipX = this._armature.flipX;
            const flipY = this._armature.flipY === yDown;
            const original = model.transform;
            const global = this.global;
            const globalMatrix = this.globalMatrix;
            const parent = this._parent;
            let dR = 0.0;

            global.copyFrom(this.local);
            if (parent) {
                const parentMatrix = parent.globalMatrix;

                if (model.inheritScale) {
                    if (!model.inheritRotation) {
                        global.rotation -= parent.global.rotation;
                    }

                    global.toMatrix(globalMatrix);
                    globalMatrix.concat(parentMatrix);

                    if (model.inheritTranslation) {
                        global.x = globalMatrix.tx;
                        global.y = globalMatrix.ty;
                    }
                    else {
                        globalMatrix.tx = global.x;
                        globalMatrix.ty = global.y;
                    }

                    global.fromMatrix(globalMatrix);
                }
                else {
                    if (model.inheritTranslation) {
                        const x = global.x;
                        const y = global.y;
                        global.x = parentMatrix.a * x + parentMatrix.c * y + parentMatrix.tx;
                        global.y = parentMatrix.d * y + parentMatrix.b * x + parentMatrix.ty;
                        parentMatrix.transformPoint(global.x, global.y, global);
                    }
                    else {
                        if (flipX) {
                            global.x = -global.x;
                        }

                        if (flipY) {
                            global.y = -global.y;
                        }
                    }

                    if (model.inheritRotation) {
                        dR = parent.global.rotation;
                        if (parentMatrix.a * parentMatrix.d - parentMatrix.b * parentMatrix.c < 0.0) {
                            dR -= global.rotation * 2.0;
                            if (parent.global.scaleX < 0.0) {
                                dR += Math.PI;
                            }

                            if (flipX != flipY || model.inheritReflection) {
                                global.skew += Math.PI;
                            }
                        }

                        global.rotation += dR;
                    }
                    else if (flipX || flipY) {
                        if (flipX && flipY) {
                            dR = Math.PI;
                        }
                        else {
                            dR = -global.rotation * 2.0;
                            if (flipX) {
                                dR += Math.PI;
                            }

                            global.rotation += Math.PI;
                        }

                        global.rotation += dR;
                    }

                    global.toMatrix(globalMatrix);
                }
            }
            else {
                if (flipX || flipY) {
                    if (flipX) {
                        global.x = -global.x;
                    }

                    if (flipY) {
                        global.y = -global.y;
                    }

                    if (flipX && flipY) {
                        dR = Math.PI;
                    }
                    else {
                        dR = -global.rotation * 2.0;
                        if (flipX) {
                            dR += Math.PI;
                        }

                        global.skew += Math.PI;
                    }

                    this.global.rotation += dR;
                }

                this.global.toMatrix(this.globalMatrix);
            }

            this._transformDirty = false;
            this._childTransformDirty = true;
        }
        else if (this._childTransformDirty) {
            this._childTransformDirty = false;
        }
    }
}
/**
 * 插槽状态。
 */
export class SlotView extends mvc.View<model.Slot, any> {
    public static toString(): string {
        return "[class dragonBones.view.SlotView]";
    }

    public displayIndex: number = -1;
    public local: geom.Transform = new geom.Transform();
    public global: geom.Transform = new geom.Transform();
    public globalMatrix: geom.Matrix = new geom.Matrix();
    public colorTransform: geom.ColorTransform = new geom.ColorTransform();
    public displayList: DisplayView<model.Display, any>[] = [];
    public display: DisplayView<model.Display, any>;
    /**
     * @internal
     * @private
     */
    public _parentDirty: boolean = false;
    /**
     * @internal
     * @private
     */
    public _displayIndexDirty: boolean = false;
    private _transformDirty: boolean = false;
    /**
     * @internal
     * @private
     */
    public _colorTransformDirty: boolean = false;
    /**
     * @internal
     * @private
     */
    public _armature: ArmatureView;
    private _parent: BoneView;

    protected _initialize(): void {
        console.assert(this._armature !== null);
        this._parentDirty = true;
    }
    /**
     * 更新。
     */
    public update(): void {
        if (this._parentDirty) {
            this._parent = this._armature.getBone(this.model.parent) !;
            console.assert(this._parent !== null);

            this._parentDirty = false;
            this._transformDirty = true;
        }

        if (this._displayIndexDirty) {

            this._displayIndexDirty = false;
            this._transformDirty = true;
            this._colorTransformDirty = true;
        }

        if (this._transformDirty && this._parent._childTransformDirty) {
            this.local.copyFrom(this.display.model.transform);
            this.global.copyFrom(this.local);
            this.global.toMatrix(this.globalMatrix);
            this.globalMatrix.concat(this._parent.globalMatrix);
            this.global.fromMatrix(this.globalMatrix);

            this._transformDirty = false;
        }

        if (this._colorTransformDirty) {

            this._colorTransformDirty = false;
        }
    }
}
/**
 * 网格状态。
 */
class MeshView extends mvc.View<model.Mesh, any> {
    public static toString(): string {
        return "[class dragonBones.view.MeshView]";
    }
    public ffd: number[] = [];

    protected _initialize(): void {
    }
    /**
     * 更新。
     */
    public update(): void {
    }
}
/**
 * 动画状态。
 */
class AnimationView extends mvc.View<model.Animation, any> {
    public static toString(): string {
        return "[class dragonBones.view.AnimationView]";
    }
    /**
     * 播放累计时间。（秒）
     */
    public time: number = 0.0;
    /**
     * 时间轴状态。
     */
    public timelines: TimelineView<model.Timeline<any, any>, any>[] = [];
    public armature: ArmatureView;

    protected _initialize(): void {
    }
    /**
     * 更新。
     */
    public update(): void {
        for (let timeline of this.timelines) {
            timeline.update();
        }
    }
}
/**
 * 时间轴状态。
 */
abstract class TimelineView<T extends model.Timeline<any, any>, Component> extends mvc.View<T, Component> {
    public static toString(): string {
        return "[class dragonBones.view.TimelineView]";
    }

    public time: number = 0.0;
    public animation: AnimationView;

    protected _getFrame<T extends model.Timeline<F, any>, F extends model.Frame<F>>(timeline: T): F {
        const frames = timeline.cacheFrames;
        const animation = this.animation.model;
        const frameRate = this.animation.armature.model.frameRate;
        const playTimes = 0;
        const duration = animation.duration / frameRate;
        const totalTime = playTimes * duration;
        let currentPlayTimes = 0;
        let time = this.time * timeline.timeScale + timeline.offset * (animation.duration / frameRate);

        if (playTimes > 0 && (time >= totalTime || time <= -totalTime)) {
            currentPlayTimes = playTimes;

            if (time < 0.0) {
                time = 0.0;
            }
            else {
                time = duration;
            }
        }
        else {
            if (time < 0.0) {
                time = -time;
                //currentPlayTimes = Math.floor(passedTime / this._duration);
                time = duration - (time % duration);
            }
            else {
                //currentPlayTimes = Math.floor(passedTime / this._duration);
                time %= duration;
            }
        }

        //time += (animationConfig.position / frameRate);

        const currentFrameIndex = Math.min(Math.floor(time * frameRate), frames.length - 1); // uint
        const currentFrame = frames[currentFrameIndex];
        this.time = time;

        return currentFrame;
    }

    protected _getTweenProgress<F extends model.TweenFrame<F>>(frame: F, time: number): number {
        const frameRate = this.animation.armature.model.frameRate;
        const duration = frame.duration / frameRate;
        if (duration <= 0.0 || frame === frame.next || frame.tweenType === dbtps.TweenType.None) {
            return 0.0;
        }

        const progress = (time - (frame.position / frameRate)) / duration;

        switch (frame.tweenType) {
            case dbtps.TweenType.Line:
                return progress;

            case dbtps.TweenType.Custom:
                return progress; // TODO
        }

        return progress;
    }

    protected _getFloatFrameValue(frame: model.FloatFrame, tweenProgress: number): number {
        const value = frame.value;
        if (tweenProgress === 0.0) {
            return value;
        }

        return value + (frame.next.value - value) * tweenProgress;
    }

    protected _getRotationFrameValue(frame: model.RotationFrame, tweenProgress: number): number {
        const value = frame.value;
        if (tweenProgress === 0.0) {
            return value;
        }

        return value + (frame.next.value - value) * tweenProgress;
    }
}

class ZOrderTimelineView extends TimelineView<model.Timeline<model.ZOrderFrame, model.Animation>, any> {

    protected _initialize(): void {
    }

    public update(): void {

    }
}

class BoneTimelineView extends TimelineView<model.Timeline<model.FloatFrame | model.RotationFrame, model.Bone>, any> {
    public bone: BoneView;

    protected _initialize(): void {
    }

    public update(): void {
        const frame = this._getFrame(this.model) as model.TweenFrame<any>;
        const tweenProgress = this._getTweenProgress(frame, this.time);
        switch (this.model.type) {
            case dbtps.TimelineType.X:
                this.bone.local.x += this._getFloatFrameValue(frame as model.FloatFrame, tweenProgress);
                break;

            case dbtps.TimelineType.Y:
                this.bone.local.y += this._getFloatFrameValue(frame as model.FloatFrame, tweenProgress);
                break;

            case dbtps.TimelineType.Rotation:
                this.bone.local.rotation += this._getRotationFrameValue(frame as model.RotationFrame, tweenProgress);
                break;

            case dbtps.TimelineType.Skew:
                this.bone.local.skew += this._getFloatFrameValue(frame as model.FloatFrame, tweenProgress);
                break;

            case dbtps.TimelineType.ScaleX:
                this.bone.local.scaleX += this._getFloatFrameValue(frame as model.FloatFrame, tweenProgress);
                break;

            case dbtps.TimelineType.ScaleY:
                this.bone.local.scaleY += this._getFloatFrameValue(frame as model.FloatFrame, tweenProgress);
                break;
        }
    }
}

class SlotTimelineView extends TimelineView<model.Timeline<model.IntFrame | model.ColorTransformFrame, model.Slot>, any> {
    public slot: SlotView;

    protected _initialize(): void {
    }

    public update(): void {
        const frame = this._getFrame(this.model) as model.TweenFrame<any>;
        const tweenProgress = this._getTweenProgress(frame, this.time);
        switch (this.model.type) {
            case dbtps.TimelineType.DisplayIndex:
                break;

            case dbtps.TimelineType.ColorTransform:
                //this.transform.y += this._getFloatFrameValue(frame as FloatFrame, tweenProgress);
                break;
        }
    }
}

class MeshTimelineView extends TimelineView<model.Timeline<model.FFDFrame, model.Mesh>, any> {
    public mesh: MeshView;

    protected _initialize(): void {
    }

    public update(): void {

    }
}

class AnimationTimelineView extends TimelineView<model.AnimationTimeline, any> {
    public animation: AnimationView;

    protected _initialize(): void {
    }

    public update(): void {
    }
}

abstract class DisplayView<M extends model.Display, Component> extends mvc.View<M, Component> {

}

class TextureDisplayView extends DisplayView<model.TextureDisplay, any> {
    protected _initialize(): void {
    }

    public update(): void {
    }
}

class MeshDisplayView extends DisplayView<model.MeshDisplay, any> {
    protected _initialize(): void {
    }

    public update(): void {
    }
}

class ArmatureDisplayView extends DisplayView<model.ArmatureDisplay, any> {
    protected _initialize(): void {
    }

    public update(): void {
    }
}

class BoundingBoxDisplayView extends DisplayView<model.BoundingBoxDisplay, any> {
    protected _initialize(): void {
    }

    public update(): void {
    }
}

class PathDisplayView extends DisplayView<model.PathDisplay, any> {
    protected _initialize(): void {
    }

    public update(): void {
    }
}